package study.web.attachment;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;

import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartRequest;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import study.domain.attachment.Attachment;
import study.domain.user.User;
import study.service.attachment.AttachmentService;
import study.service.web.SessionService;

import com.google.common.collect.Lists;
import com.google.gson.Gson;

/**
 * 첨부파일 컨트롤러
 *
 * @author Barney Kim
 */
@Slf4j
@Controller
@RequestMapping(value = "/attachments")
public class AttachmentController implements HandlerExceptionResolver {

	public static final int MAX_IMAGE_SIZE = 1 * 1024 * 1024; // 1MB;

	public static final String PUBLIC_CATEGORY = "public";

	@Resource(name = "sessionService")
	private SessionService sessionService;

	@Resource(name = "attachmentService")
	private AttachmentService attachmentService;

	@RequestMapping(value = "/imageUpload", method = RequestMethod.POST)
	@ResponseBody
	public String imageUpload(
			HttpServletRequest request,
			MultipartRequest mrequest,
			@RequestParam(required = false, defaultValue = PUBLIC_CATEGORY) String category,
			@RequestParam(required = false) String groupId) {
		return upload(request, mrequest, category, groupId, "image", MAX_IMAGE_SIZE);
	}

	/**
	 * Ajax.form을 사용하여 파일 업로드를 할 때 Firefox에서는 @ResponseBody를 사용하면 iframe 방식으로
	 * 제대로 처리가 안되는 문제가 있기 때문에 결과를 JSON으로 바꾸고 일반 텍스트로 출력한다.
	 *
	 * @param request
	 * @param mrequest
	 * @param category
	 * @param fileType
	 * @param maxSize
	 */
	@RequestMapping(value = "/upload", method = RequestMethod.POST)
	@ResponseBody
	public String upload(
			HttpServletRequest request,
			MultipartRequest mrequest,
			@RequestParam(required = false, defaultValue = PUBLIC_CATEGORY) String category,
			@RequestParam(required = false) String groupId,
			@RequestParam(required = false) String fileType,
			@RequestParam(required = false, defaultValue = "0") long maxSize) {

		AttachmentResult result = new AttachmentResult();
		User user = sessionService.getCurrentUser();
		if (user == null) {
			result.setCause("need_login");
			return new Gson().toJson(result);
		}

		List<Attachment> attachments = Lists.newArrayList();
		List<MultipartFile> files = mrequest.getFiles("file");
		result.setTotal(files.size());
		int uploadedCount = 0;
		if (!Attachment.isUuid(groupId)) {
			groupId = Attachment.generateUuid();
		}
		for (MultipartFile file : files) {
			if (file.isEmpty()) {
				log.warn("file is empty.");
				continue;
			}
			if (maxSize != 0 && file.getSize() > maxSize) {
				log.warn(
						"filesize exceed file={}, size={}, maxSize={}",
						new Object[] { file.getOriginalFilename(),
								file.getSize(), maxSize });
				result.setCause("size_exceed");
				continue;
			}
			if (fileType != null && !file.getContentType().startsWith(fileType)) {
				log.warn("filetype mismatch file={}, contentType={}",
						file.getOriginalFilename(), file.getContentType());
				result.setCause("filetype_mismatch");
				continue;
			}

			Attachment attachment = attachmentService.addAttachment(user, category, groupId, file);
			if (attachment != null) {
				attachments.add(attachment);
				uploadedCount++;
			}
			log.debug("attachment={}", attachment);
		}
		if (!result.isError() && uploadedCount == 0) {
			result.setCause("file_empty");
		}
		result.setUploadedCount(uploadedCount);
		result.setAttachments(attachments);
		return new Gson().toJson(result);
	}

	@RequestMapping(value = "/{category}/{year}/{month}/{filename}.{ext}",
			method = RequestMethod.GET)
	public void download(HttpServletRequest request,
			HttpServletResponse response,
			@PathVariable("category") String category,
			@PathVariable("year") String year,
			@PathVariable("month") String month,
			@PathVariable("filename") String filename,
			@PathVariable("ext") String ext) throws ServletException,
			IOException {

		String filepath = String.format("/%s/%s/%s/%s.%s", category, year,
				month, filename, ext);

		File file = attachmentService.getAttachmentFile(filepath);
		long attachmentExpires = attachmentService.getAttachmentExpires();
		download(request, response, file, attachmentExpires);
	}

	/**
	 * 지정된 파일을 다운로드 한다.
	 *
	 * @param request
	 * @param response
	 * @param file
	 * @param expires
	 *            캐시 유지 시간 ms. 0이면 헤더 생략.
	 */
	private void download(HttpServletRequest request,
			HttpServletResponse response, File file, long expires)
			throws ServletException, IOException {

		String mimeType = request.getSession().getServletContext()
				.getMimeType(file.getName());

		if (file == null || !file.exists() || file.length() <= 0
				|| file.isDirectory()) {
			response.setStatus(HttpServletResponse.SC_NOT_FOUND);
			response.getWriter().close();
			return;
		}

		if (mimeType == null || mimeType.length() == 0) {
			mimeType = "application/octet-stream;";
		}
		response.setContentType(mimeType + "; charset=UTF-8");
		if (expires > 0) {
			response.addDateHeader("Expires", System.currentTimeMillis()
					+ expires);
		}
		String userAgent = request.getHeader("User-Agent");
		if (userAgent.indexOf("MSIE") > -1) {
			response.setHeader(
					"Content-Disposition",
					String.format("filename=%s;",
							URLEncoder.encode(file.getName(), "UTF-8")));
		} else { // FF, WebKit
			response.setHeader("Content-Disposition", String.format(
					"filename=%s;", new String(
							file.getName().getBytes("UTF-8"), "latin1")));
		}
		if (file.length() > 0) {
			response.setHeader("Content-Length", String.valueOf(file.length()));
		}

		BufferedInputStream instram = null;
		BufferedOutputStream outstram = null;
		try {
			instram = new BufferedInputStream(new FileInputStream(file));
			outstram = new BufferedOutputStream(response.getOutputStream());
			int read = 0;
			byte[] buffer = new byte[64 * 1024]; // 64KB
			while ((read = instram.read(buffer)) != -1) {
				outstram.write(buffer, 0, read);
			}
		} finally {
			IOUtils.closeQuietly(outstram);
			IOUtils.closeQuietly(instram);
		}
	}

	@Override
	public ModelAndView resolveException(HttpServletRequest request,
			HttpServletResponse response, Object handler, Exception ex) {
		String uri = request.getRequestURI();
		if (!uri.startsWith("/attachments")) {
			return null;
		}

		AttachmentResult result = new AttachmentResult();
		if (ex instanceof MaxUploadSizeExceededException) {
			// long serverMaxSize =
			// ((MaxUploadSizeExceededException)ex).getMaxUploadSize();
			// serverMaxSize / (1024*1000)+"MB"
			result.setCause("server_maxsize_exceed");
		}
		try {
			response.setContentType("text/plain; charset=UTF-8");
			response.getWriter().write(new Gson().toJson(result));
			response.getWriter().close();
		} catch (IOException e) {
			// ignore
		}
		return null;
	}

}
